/*
 * Copyright (c) 2012-2018 Red Hat, Inc.
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Red Hat, Inc. - initial API and implementation
 */
package org.eclipse.che.ide.menu;

import static org.eclipse.che.ide.util.dom.Elements.disableTextSelection;

import com.google.gwt.dom.client.Element;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;
import com.google.inject.Provider;
import elemental.html.DivElement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.che.ide.api.action.Action;
import org.eclipse.che.ide.api.action.ActionEvent;
import org.eclipse.che.ide.api.action.ActionGroup;
import org.eclipse.che.ide.api.action.ActionManager;
import org.eclipse.che.ide.api.action.ActionSelectedHandler;
import org.eclipse.che.ide.api.action.CustomComponentAction;
import org.eclipse.che.ide.api.action.IdeActions;
import org.eclipse.che.ide.api.action.Presentation;
import org.eclipse.che.ide.api.action.Separator;
import org.eclipse.che.ide.api.keybinding.KeyBindingAgent;
import org.eclipse.che.ide.api.parts.PerspectiveManager;
import org.eclipse.che.ide.command.toolbar.CommandToolbarPresenter;
import org.eclipse.che.ide.ui.toolbar.CloseMenuHandler;
import org.eclipse.che.ide.ui.toolbar.MenuLockLayer;
import org.eclipse.che.ide.ui.toolbar.PresentationFactory;
import org.eclipse.che.ide.util.dom.Elements;

/**
 * Implements {@link MainMenuView} using standard GWT Menu Widgets
 *
 * @author Nikolay Zamosenchuk
 * @author Oleksii Orel
 */
public class MainMenuViewImpl extends Composite
    implements MainMenuView, CloseMenuHandler, ActionSelectedHandler {
  private final MenuResources resources;
  private final Provider<PerspectiveManager> managerProvider;

  private final PresentationFactory presentationFactory = new PresentationFactory();
  /** Working table, cells of which are contains element of Menu. */
  private final MenuBarTable table = new MenuBarTable();

  private final FlowPanel rightPanel = new FlowPanel();

  private final FlowPanel leftPanel = new FlowPanel();
  /** Panel, which contains top menu. */
  private final FlowPanel rootPanel = new FlowPanel();
  /** Lock layer for displaying popup menus. */
  private MenuLockLayer lockLayer;
  /** List Menu Bar items. */
  private Map<Element, MenuBarItem> menuBarItems = new LinkedHashMap<>();

  private Map<Action, MenuBarItem> action2barItem = new HashMap<>();

  /** Store selected Menu Bar item. */
  private MenuBarItem selectedMenuBarItem;

  private List<Action> leftVisibleActions = new ArrayList<>();
  private List<Action> menuVisibleActions = new ArrayList<>();
  private ActionManager actionManager;
  private KeyBindingAgent keyBindingAgent;

  /** Create new {@link MainMenuViewImpl} */
  @Inject
  public MainMenuViewImpl(
      MenuResources resources,
      ActionManager actionManager,
      KeyBindingAgent keyBindingAgent,
      Provider<PerspectiveManager> managerProvider,
      CommandToolbarPresenter toolbarPresenter) {
    this.resources = resources;
    this.actionManager = actionManager;
    this.keyBindingAgent = keyBindingAgent;
    this.managerProvider = managerProvider;

    initWidget(rootPanel);
    disableTextSelection(rootPanel.getElement(), true);

    rootPanel.setStyleName(resources.menuCss().menuBar());

    leftPanel.addStyleName(resources.menuCss().leftPanel());

    table.setStyleName(resources.menuCss().menuBarTable());
    table.setCellPadding(0);
    table.setCellSpacing(0);

    final DivElement triangleSeparator =
        Elements.createDivElement(resources.menuCss().triangleSeparator());

    rightPanel.addStyleName(resources.menuCss().rightPanel());
    rightPanel.addStyleName(resources.menuCss().commandToolbar());

    rootPanel.add(leftPanel);
    rootPanel.add(table);
    rootPanel.getElement().appendChild((Element) triangleSeparator);
    rootPanel.add(rightPanel);

    toolbarPresenter.go(rightPanel::add);
  }

  @Override
  public void setDelegate(ActionDelegate delegate) {
    new Timer() {
      @Override
      public void run() {
        updateMenuActions();
      }
    }.scheduleRepeating(1000);
  }

  /** Handle closing of all popup windows. */
  public void onCloseMenu() {
    selectedMenuBarItem.setNormalState();
    selectedMenuBarItem = null;
    lockLayer = null;
  }

  void updateMenuActions() {
    if (selectedMenuBarItem != null) {
      return;
    }

    List<Action> newMenuVisibleActions = new ArrayList<>();
    expandActionGroup(IdeActions.GROUP_MAIN_MENU, newMenuVisibleActions, actionManager);
    if (!newMenuVisibleActions.equals(menuVisibleActions)) {
      removeAll();
      for (final Action action : newMenuVisibleActions) {
        add(action, presentationFactory);
      }
      menuVisibleActions = newMenuVisibleActions;
    }
    List<Action> newLeftVisibleActions = new ArrayList<>();
    expandActionGroup(IdeActions.GROUP_LEFT_MAIN_MENU, newLeftVisibleActions, actionManager);
    if (!newLeftVisibleActions.equals(leftVisibleActions)) {
      leftPanel.clear();
      for (Action action : newLeftVisibleActions) {
        addToPanel(leftPanel, action, presentationFactory);
      }
      leftVisibleActions = newLeftVisibleActions;
    }
  }

  /** Create a new widget and add it to panel menu. */
  private void addToPanel(FlowPanel panel, Action action, PresentationFactory presentationFactory) {
    Presentation presentation = presentationFactory.getPresentation(action);

    if (action instanceof Separator) {
      panel.add(new SeparatorItem(resources.menuCss().panelSeparator()));

      // todo find way to render non custom actions
    } else if (action instanceof CustomComponentAction) {
      CustomComponentAction customComponentAction = (CustomComponentAction) action;
      Widget component = customComponentAction.createCustomComponent(presentation);
      component.addStyleName(resources.menuCss().customComponent());
      panel.add(component);
    }
  }

  private void removeAll() {
    table.clear();
    menuBarItems.clear();
    action2barItem.clear();
  }

  private void expandActionGroup(
      String actionGroupId, final List<Action> newVisibleActions, ActionManager actionManager) {
    final ActionGroup mainActionGroup = (ActionGroup) actionManager.getAction(actionGroupId);
    if (mainActionGroup == null) return;
    expandActionGroup(newVisibleActions, actionManager, mainActionGroup);
  }

  private void expandActionGroup(
      List<Action> newVisibleActions, ActionManager actionManager, ActionGroup mainActionGroup) {
    final Action[] children = mainActionGroup.getChildren(null);
    for (final Action action : children) {
      final Presentation presentation = presentationFactory.getPresentation(action);
      final ActionEvent e = new ActionEvent(presentation, actionManager);
      action.update(e);
      if (presentation.isVisible()) { // add only visible items
        newVisibleActions.add(action);
      }
      if (action2barItem.containsKey(action)) {
        action2barItem.get(action).update();
      }
    }
  }

  /** Create and add new item in menu. */
  private void add(Action action, PresentationFactory presentationFactory) {
    Presentation presentation = presentationFactory.getPresentation(action);
    if (action instanceof ActionGroup) {
      ActionGroup group = (ActionGroup) action;
      table.setText(0, menuBarItems.size(), presentation.getText());
      Element element = table.getCellFormatter().getElement(0, menuBarItems.size());
      MenuBarItem item =
          new MenuBarItem(
              group,
              actionManager,
              managerProvider,
              presentationFactory,
              element,
              this,
              keyBindingAgent,
              resources.menuCss());

      item.onMouseOut();
      menuBarItems.put(element, item);
      action2barItem.put(group, item);
    } else if (action instanceof CustomComponentAction) {
      Widget widget = ((CustomComponentAction) action).createCustomComponent(presentation);
      table.setWidget(0, menuBarItems.size(), widget);
      Element element = table.getCellFormatter().getElement(0, menuBarItems.size());
      menuBarItems.put(element, null);
    }
  }

  /**
   * Open Popup Menu.
   *
   * @param item popup menu item.
   */
  public void openPopupMenu(MenuBarItem item) {
    if (lockLayer == null) {
      int top = getAbsoluteTop() + getOffsetHeight();
      lockLayer = new MenuLockLayer(this, top);
    }

    item.openPopupMenu(lockLayer);
  }

  @Override
  public void onActionSelected(Action action) {
    if (action2barItem.containsKey(action)) {
      MenuBarItem item = action2barItem.get(action);
      if (selectedMenuBarItem != null && selectedMenuBarItem != item) {
        selectedMenuBarItem.setNormalState();
        selectedMenuBarItem.closePopupMenu();
      }

      selectedMenuBarItem = item;
    } else {
      lockLayer.close();
      lockLayer = null;
    }
  }

  private static class SeparatorItem extends Composite {
    public SeparatorItem(String styleName) {
      final FlowPanel widget = new FlowPanel();
      widget.addStyleName(styleName);
      Element separator = widget.getElement();
      for (int i = 0; i < 6; i++) {
        separator.appendChild(DOM.createDiv());
      }

      initWidget(widget);
    }
  }

  /** This is visual component. Uses for handling mouse events on MenuBar. */
  private class MenuBarTable extends FlexTable {

    public MenuBarTable() {
      sinkEvents(Event.ONMOUSEOVER | Event.ONMOUSEOUT | Event.ONMOUSEDOWN);
    }

    @Override
    public void onBrowserEvent(Event event) {
      Element td = getEventTargetCell(event);
      MenuBarItem item = menuBarItems.get(td);

      if (item == null) {
        return;
      }

      switch (DOM.eventGetType(event)) {
        case Event.ONMOUSEOVER:
          if (selectedMenuBarItem != null && item != selectedMenuBarItem) {
            if (item.onMouseDown()) {
              openPopupMenu(item);
            }
          }

          item.onMouseOver();
          break;

        case Event.ONMOUSEOUT:
          item.onMouseOut();
          break;

        case Event.ONMOUSEDOWN:
          if (item == selectedMenuBarItem) {
            if (lockLayer != null) {
              lockLayer.close();
            }
            return;
          }

          if (item.onMouseDown()) {
            openPopupMenu(item);
          }
          break;

        default:
          break;
      }
    }
  }
}
